/**
 * 将 .page 文件加载到向量数据库中
 *
 * 该函数会获取 knowledge 目录下的所有 .page 文件，
 *
 * 逐个读取文件内容并验证格式，将符合条件的知识片段添加到向量数据库中，
 *
 * 最后显示加载成功的信息。
 */
async function loadPagesIntoVectorDB() {
	try {
		/** 获取 knowledge 目录下的所有文件 */
		const response = await fetch('/files/knowledge');
		// 如果请求失败，直接返回
		if (!response.ok) return;
		/** 解析响应数据为 JSON 格式 */
		const files = await response.json();
		/** 过滤出所有 .page 文件 */
		const pageFiles = files.filter(f => f.name.endsWith('.page'));
		/** 记录成功加载的知识片段数量 */
		let loadedCount = 0;
		// 遍历所有 .page 文件
		for (const file of pageFiles) {
			/** 读取单个 .page 文件的内容 */
			const response = await fetch(`/read/knowledge/${file.name}`);
			// 如果读取失败，跳过当前文件
			if (!response.ok) continue;
			/** 获取文件内容的文本 */
			const content = await response.text();
			// 如果文件内容为空，跳过当前文件
			if (!content.trim()) continue;
			/** 解析文件内容为 JSON 对象 */
			const knowledgeData = JSON.parse(content);
			/** 解构获取知识数据中的消息、向量和时间戳 */
			const { message, vector, timestamp } = knowledgeData;
			// 验证知识数据格式是否有效
			if (!message || !vector || !Array.isArray(vector)) throw new Error(`知识文件格式无效 -> ${file.name}`);
			/** 构建唯一的键 */
			const key = `knowledge:page:${file.name}`;
			// 如果向量数据库中已存在该键，跳过当前文件
			if (knowledgeVectorStore.has(key)) continue;
			// 将知识片段添加到向量数据库中
			vectorAddKnowledge(key, message, vector, timestamp);
			// 成功加载数量加 1
			loadedCount++;
			// 显示单个文件加载成功的消息
			tracelessRenderMessage(`✅ 加载知识库文件: ${file.name}`, simpleKnowledgePanel);
		};
		// 显示总的加载成功消息
		tracelessRenderMessage(`✅ 从 .page 文件加载了 ${loadedCount} 个知识片段，知识库总数: ${knowledgeVectorStore.size}`, simpleKnowledgePanel);
	}
	catch (error) {
		// 捕获异常并显示错误信息
		showSystemMessage(`${error.name} | ${error.message} | ${error.stack}`, "error");
	}
};

/**
 * 将文件分块保存为 .page 文件，并将其内容嵌入向量数据库
 *
 * @param {Array} fileChunks - 文件分块数组，每个分块对象应包含 chunkIndex 和 content 属性
 */
async function saveFileChunksAsPages(fileChunks) {
	/** 获取 knowledge 目录下的所有文件和文件夹信息 */
	const response = await fetch('/files/knowledge');
	/** 将响应数据解析为 JSON 格式 */
	const files = await response.json();
	/** 过滤出所有 .page 文件，排除目录 */
	const existingPages = files.filter(file => file.name.endsWith('.page') && !file.isDir);
	/** 计算新文件的起始编号，基于已有的 .page 文件数量 */
	const startIndex = existingPages.length;
	/** 记录成功保存并嵌入的文件数量 */
	let savedCount = 0;
	// 遍历所有文件分块
	for (let i = 0; i < fileChunks.length; i++) {
		/** 获取当前文件分块 */
		const chunk = fileChunks[i];
		/** 计算当前文件的全局编号 */
		const globalPageNumber = startIndex + i;
		/** 生成文件名，格式为 4 位全局编号 + 3 位分块编号 + .page */
		const pageFileName = `${globalPageNumber.toString().padStart(4, '0')}_${chunk.chunkIndex.toString().padStart(3, '0')}.page`;
		/** 获取当前系统时间作为时间戳 */
		const timestamp = getSystemTime();
		/** 构建包含时间戳和内容的消息 */
		const message = `## 时间\n\n${timestamp}\n\n## 内容\n\n${chunk.content}`;
		/** 获取消息的向量嵌入 */
		const embedding = await vectorGetEmbedding(message);
		/** 如果向量嵌入生成失败，跳过当前分块 */
		if (!embedding || embedding.length === 0) continue;
		/** 获取第一个向量嵌入作为当前消息的向量表示 */
		const vector = embedding[0];
		// 创建知识库数据对象
		const knowledgeData = {
			message: message,
			vector: vector,
			timestamp: timestamp
		};
		/** 对文件名进行 Base64 编码 */
		const encodedFileName = toBtoaString(`knowledge/${pageFileName}`);
		/** 发送保存请求，将知识库数据保存为文件 */
		const saveRes = await fetch('/save',
			{
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					'X-File-Name': encodedFileName,
					'X-Overwrite': 'true'
				},
				body: JSON.stringify(knowledgeData, null, 2)
			}
		);
		/** 如果保存失败，跳过当前分块 */
		if (!saveRes.ok) continue;
		/** 成功保存数量加 1 */
		savedCount++;
		/** 构建向量数据库中的唯一键 */
		const key = `knowledge:page:${pageFileName}`;
		/** 将知识片段添加到向量数据库中 */
		vectorAddKnowledge(key, message, vector, timestamp);
		/** 显示单个文件保存并嵌入成功的消息 */
		tracelessRenderMessage(`✅ 保存并嵌入知识库文件: ${pageFileName}`, simpleKnowledgePanel);
	}
	/** 显示总的保存并嵌入成功的消息 */
	tracelessRenderMessage(`✅ 保存并嵌入 ${savedCount} 个知识库文件`, simpleKnowledgePanel);
};

/**
 * 处理入口目录下的文件，将文件读取并分块，最后清空入口目录
 *
 * @returns {Promise<Array>} 返回所有文件分块组成的数组
 */
async function processEntryDirectoryFiles() {
	/** 获取入口目录下的文件列表 */
	const entryFiles = await getEntryDirectoryFiles();
	// 如果未获取到文件或文件列表为空，直接返回空数组
	if (!entryFiles || entryFiles.length === 0) return [];
	/** 用于存储所有文件的分块 */
	const allFileChunks = [];
	// 遍历入口目录下的所有文件
	for (const file of entryFiles) {
		// 如果是目录，则跳过当前循环
		if (file.isDir) continue;
		/** 读取文件并将其分块 */
		const fileChunks = await readAndChunkFile(file, MAX_CHUNK_SIZE);
		// 将当前文件的分块添加到总的分块数组中
		allFileChunks.push(...fileChunks);
	}
	// 清空入口目录下的文件
	await clearEntryDirectoryFiles(entryFiles);
	// 返回所有文件的分块数组
	return allFileChunks;
};

/**
 * 处理 knowledge 目录下的文件：获取目录结构，处理入口目录文件，将文件分块保存为 .page 文件并嵌入向量数据库
 */
async function processKnowledgeFiles() {
	try {
		// 获取 knowledge 目录的结构
		await getKnowledgeDirectoryStructure();
		/** 处理入口目录下的文件，获取所有文件的分块 */
		const fileChunks = await processEntryDirectoryFiles();
		// 如果未获取到文件分块，直接返回
		if (fileChunks.length == 0) return;
		// 调用函数将文件分块保存为 .page 文件并嵌入向量数据库
		await saveFileChunksAsPages(fileChunks);
	}
	catch (error) {
		// 捕获异常并显示错误信息
		showSystemMessage(`${error.name} | ${error.message} | ${error.stack}`, "error");
	}
};

/**
 * 重载向量数据库
 *
 * 该函数会先清空向量数据库，然后重新加载 .page 文件内容到向量数据库中，
 *
 * 最后打印重载完成的信息及当前向量库中的嵌入向量数量。
 */
async function reloadDatabase() {
	// 清空向量数据库
	vectorClear();
	// 加载 .page 文件内容到向量数据库
	await loadPagesIntoVectorDB();
	// 打印数据库重载完成信息及当前向量库中的嵌入向量数量
	const totalVectors = vectorStore.size + knowledgeVectorStore.size;
	// 打印数据库重载完成信息及当前向量库中的嵌入向量数量
	tracelessRenderMessage(`✅ 数据库重载完成，共加载 ${totalVectors} 个嵌入向量。`, simpleKnowledgePanel);
}